﻿using System;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq;
using BaMusaSoft.RPS.DataModel.Exceptions;
using BaMusaSoft.RPS.DataModel.Repositories;

namespace BaMusaSoft.RPS.DataModel
{
    public class UnitOfWork : IUnitOfWork
    {
        #region Fields

        private RepositoryBase<ClaimDetail> _claimDetails;
        private RepositoryBase<ClaimNumber> _claimNumbers;
        private RepositoryBase<Claim> _claims;
        private RpsEntities _context = new RpsEntities();
        private RepositoryBase<ContractActivity> _contractActivities;
        private RepositoryBase<Contract> _contracts;
        private RepositoryBase<Customer> _customers;
        private RepositoryBase<Outbox> _outboxes;
        private RepositoryBase<PaymentType> _paymentTypes;
        private RepositoryBase<Payment> _payments;
        private RepositoryBase<Property> _properties;
        private RepositoryBase<ScheduleDetail> _scheduleDetails;
        private RepositoryBase<SchedulePayment> _schedulePayments;
        private RepositoryBase<Schedule> _schedules;
        private RepositoryBase<Signer> _signer;

        #endregion

        #region Contsturctor

        public UnitOfWork()
        {
            _context = new RpsEntities();
        }

        #endregion

        #region Implementation of IDisposable

        private bool _disposed;

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (_context != null)
                    {
                        _context.Dispose();
                        _context = null;
                    }
                }
            }
            _disposed = true;
        }

        #endregion

        #region Implementation of IUnitOfWork

        /// <summary>
        /// Gets the claims.
        /// </summary>
        public RepositoryBase<Claim> Claims
        {
            get { return _claims ?? (_claims = new ClaimsRepository(_context)); }
        }

        /// <summary>
        /// Gets the claim details.
        /// </summary>
        public RepositoryBase<ClaimDetail> ClaimDetails
        {
            get { return _claimDetails ?? (_claimDetails = new ClaimDetailsRepository(_context)); }
        }

        /// <summary>
        /// Gets the claim numbers.
        /// </summary>
        public RepositoryBase<ClaimNumber> ClaimNumbers
        {
            get { return _claimNumbers ?? (_claimNumbers = new ClaimNumbersRepository(_context)); }
        }

        /// <summary>
        /// Gets the contracts.
        /// </summary>
        public RepositoryBase<Contract> Contracts
        {
            get { return _contracts ?? (_contracts = new ContractsRepository(_context)); }
        }

        /// <summary>
        /// Gets the contract activities.
        /// </summary>
        public RepositoryBase<ContractActivity> ContractActivities
        {
            get { return _contractActivities ?? (_contractActivities = new ContractActivitiesRepository(_context)); }
        }

        /// <summary>
        /// Gets the customers.
        /// </summary>
        public RepositoryBase<Customer> Customers
        {
            get { return _customers ?? (_customers = new CustomersRepository(_context)); }
        }

        /// <summary>
        /// Gets the outboxes.
        /// </summary>
        public RepositoryBase<Outbox> Outboxes
        {
            get { return _outboxes ?? (_outboxes = new OutboxesRepository(_context)); }
        }

        /// <summary>
        /// Gets the payments.
        /// </summary>
        public RepositoryBase<Payment> Payments
        {
            get { return _payments ?? (_payments = new PaymentsRepository(_context)); }
        }

        /// <summary>
        /// Gets the payment types.
        /// </summary>
        public RepositoryBase<PaymentType> PaymentTypes
        {
            get { return _paymentTypes ?? (_paymentTypes = new PaymentTypesRepository(_context)); }
        }

        /// <summary>
        /// Gets the properties.
        /// </summary>
        public RepositoryBase<Property> Properties
        {
            get { return _properties ?? (_properties = new PropertiesRepository(_context)); }
        }

        /// <summary>
        /// Gets the schedule details.
        /// </summary>
        public RepositoryBase<ScheduleDetail> ScheduleDetails
        {
            get { return _scheduleDetails ?? (_scheduleDetails = new ScheduleDetailsRepository(_context)); }
        }

        /// <summary>
        /// Gets the schedules.
        /// </summary>
        public RepositoryBase<Schedule> Schedules
        {
            get { return _schedules ?? (_schedules = new SchedulesRepository(_context)); }
        }

        /// <summary>
        /// Gets the schedule payments.
        /// </summary>
        public RepositoryBase<SchedulePayment> SchedulePayments
        {
            get { return _schedulePayments ?? (_schedulePayments = new SchedulePaymentsRepository(_context)); }
        }

        /// <summary>
        /// Gets the signers.
        /// </summary>
        public RepositoryBase<Signer> Signers
        {
            get { return _signer ?? (_signer = new SignerRepository(_context)); }
        }

        /// <summary>
        /// Gets a value indicating whether this unit of work has changes.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has changes; otherwise, <c>false</c>.
        /// </value>
        public bool HasChanges
        {
            get
            {
                DbChangeTracker changeTracker = _context.ChangeTracker;
                changeTracker.DetectChanges();
                bool changed = changeTracker.Entries().Any(x => x.State == EntityState.Added ||
                                                                x.State == EntityState.Deleted ||
                                                                x.State == EntityState.Modified);
                return changed;
            }
        }

        /// <summary>
        /// Saves all changes made to current unit of work.
        /// </summary>
        public void Save()
        {
            try
            {
                _context.SaveChanges();
                RaiseStateChanges();
            }
            catch (Exception ex)
            {
                string msg = @"An error accured while saving data. See the following details: ";
                throw new DataModelException(msg, ex);
            }
        }

        public event EventHandler StateChanged;

        /// <summary>
        /// Resets current unit of work. This will discard all changes made since last call to Save method.
        /// </summary>
        public void Reset()
        {
            _context.Dispose();
            _context = null;
            ResetRepositories();
        }

        #endregion

        #region Helpers

        private void RaiseStateChanges()
        {
            EventHandler handler = StateChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        private void ResetRepositories()
        {
            _claimDetails = null;
            _claimNumbers = null;
            _claims = null;
            _contractActivities = null;
            _contracts = null;
            _customers = null;
            _outboxes = null;
            _paymentTypes = null;
            _payments = null;
            _properties = null;
            _scheduleDetails = null;
            _schedulePayments = null;
            _schedules = null;
            _signer = null;
        }

        #endregion
    }
}